package spea;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import common.BasicAgent;
import common.GeneticHelper;
import common.GeneticOperations;

/*
 *	Single organism, which belong to population 
 */

public class Individual<Genotype> implements BasicAgent<Genotype>{
	private Genotype genotype;
	private GeneticOperations<Genotype> go;
	private List<Double> values = null;
	private long strength = 0; //number of solution, which this creature dominates
	private List<Individual<Genotype>> dominated;
	private long rowFitness = 0;
	private Double distance = 0.0;
	private double density = 0.0;
	private double fitness = 0.0;
	
	private List<Double> fitnesses = null;

	public Individual(Genotype genotype, GeneticOperations<Genotype> go) {
		this.genotype = genotype;
		this.go = go;

		calculateValues();

		dominated = new ArrayList<Individual<Genotype>>();
	}


	public List<Individual<Genotype>> crossover(Individual<Genotype> c2)
	{
		List<Individual<Genotype>> result = new LinkedList<Individual<Genotype>>();
		List<Genotype> childrenGenotypes = go.crossover(this.getGenotype(), c2.getGenotype());
		for (Genotype genotype : childrenGenotypes) {
			result.add(new Individual<Genotype>(genotype,this.go));
		}
		return result;
	}

	/**
	 * -1 jestem lepszy
	 * 0  remis
	 * 1  jestem gorszy
	 * @param ind
	 * @return
	 */
	public int checkDomination(Individual<Genotype> c) {

		int result = 0;
		result = 0;
		if (this.dominate(c)) {
			result = -1;
		}
		else if (c.dominate(this)) {
			result = 1;
		}

		return result;
	}

	private boolean dominate(Individual<Genotype> c) {

		boolean result = true;
		boolean one_better = false;
		for (int i = 0; i < this.getValues().size(); i++) {
			if (this.getValue(i) < c.getValue(i)) { // we want to minimize
				one_better = true;
			}
			if (this.getValue(i) > c.getValue(i)) { // if at last one is worse
				result = false;
			}
		}
		if (!one_better) {
			result = false;
		}
		return result;
	}


	public void mutate() 
	{    
		go.mutate(genotype);
		calculateValues();
	}

	public long getStrength() {
		return this.strength;
	}

	public List<Individual<Genotype>> getDominated() {
		return this.dominated;
	}

	public void incRowFitness(long rowFitness) {
		this.rowFitness += rowFitness;
	}

	public void zero() {
		this.strength = 0;
		this.rowFitness = 0;
		this.dominated.clear();
	}

	public long getRowFitness() {
		return rowFitness;
	}

	public double getDensity() {
		return this.density;
	}

	public void calculateFitness() {
		this.fitness = this.rowFitness + this.density;
	}

	public double getFitness() {
		return this.fitness;
	}

	public void calculateStrength(List<Individual<Genotype>> population,
			List<Individual<Genotype>> archive) {
		long strenght = 0;		
		List<Individual<Genotype>> dominated = new ArrayList<Individual<Genotype>>();

		for (Individual<Genotype> creatureTmp: population) {
			if (!creatureTmp.equals(this)) {
				if ( ((creatureTmp.getValue(0) >= this.getValue(0)) & (creatureTmp
						.getValue(1) >= this.getValue(1)))
						& ((creatureTmp.getValue(0) > this.getValue(0)) | (creatureTmp
								.getValue(1) > this.getValue(1))) ) {
					strenght++;
					dominated.add(creatureTmp);
				}
			}
		}

		for (Individual<Genotype> creatureTmp: archive) {
			if (!creatureTmp.equals(this)) {
				if ( ((creatureTmp.getValue(0) >= this.getValue(0)) & (creatureTmp
						.getValue(1) >= this.getValue(1)))
						& ((creatureTmp.getValue(0) > this.getValue(0)) & (creatureTmp
								.getValue(1) > this.getValue(1)))) {
					strenght++;
					dominated.add(creatureTmp);
				}
			}
		}

		this.strength = strenght;
		this.dominated = dominated;
	}

	public void calculateRowFitness() {
		for (Individual<Genotype> dominatedCreature: this.dominated) {
			dominatedCreature.incRowFitness(this.getStrength());
		}
	}

	//calculate distance to other creatures and write it in there
	public void calculateDistance(List<Individual<Genotype>> population,
			List<Individual<Genotype>> archive) {

		double distance = 0.0;

		for (Individual<Genotype> creatureTmp: population) {
			if (!creatureTmp.equals(this)) {
				distance = Math.sqrt((this.getValue(0) - creatureTmp.getValue(0))
						* (this.getValue(0) - creatureTmp.getValue(0))
						+ (this.getValue(1) - creatureTmp.getValue(1))
						* (this.getValue(1) - creatureTmp.getValue(1)));
			}
			creatureTmp.setDistance(distance);
		}

		for (Individual<Genotype> creatureTmp: archive) {
			if (!creatureTmp.equals(this)) {
				distance = Math.sqrt((this.getValue(0) - creatureTmp.getValue(0))
						* (this.getValue(0) - creatureTmp.getValue(0))
						+ (this.getValue(1) - creatureTmp.getValue(1))
						* (this.getValue(1) - creatureTmp.getValue(1)));
			}
			creatureTmp.setDistance(distance);
		}
	}

	public void setDistance(Double distance) {
		this.distance = distance;
	}

	public Double getDistance() {
		return this.distance;
	}

	public void calculateDensity(List<Individual<Genotype>> population,
			List<Individual<Genotype>> archive, int k) {

		DistanceComparator<Genotype> dc = new DistanceComparator<Genotype>();
		List<Individual<Genotype>> allCreature = new ArrayList<Individual<Genotype>>();
		double distanceTmp;

		this.calculateDistance(population, archive);
		allCreature.addAll(population);
		allCreature.remove(this);
		allCreature.addAll(archive);
		Collections.sort(allCreature, dc);

		distanceTmp = allCreature.get(k-1).getDistance();

		this.density = 1.0 / (distanceTmp + 2.0);
		this.calculateFitness();
	}

	public void calculateValues() {
		this.values = go.getValues(genotype);
		this.fitnesses = go.getFitnessesFromValue(this.values);
	}

	public List<Double> getValues() {
		return this.values;
	}

	public double getValue(int i) {
		return this.values.get(i);
	}

	public Genotype getGenotype() {
		return genotype;
	}


	@Override
	public int checkDomination(BasicAgent<Genotype> agent) {
		 return GeneticHelper.checkDomination(this, agent);
	}


	@Override
	public double getFitness(int index) {
		return this.fitnesses.get(index);
	}


	@Override
	public List<Double> getFitnesses() {
		return this.fitnesses;
	}
}
